package br.ufrj.dcc.xml;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.text.JTextComponent;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import uxquerydemo.utils.StatisticsCollector;
import br.ufrj.dcc.xml.database.DatabaseSettings;
import br.ufrj.dcc.xml.database.ItemManager;
import br.ufrj.dcc.xml.dynamicintervals.XMLManager;
import br.ufrj.dcc.xml.dynamicintervals.encoding.DatabaseException;
import br.ufrj.dcc.xml.dynamicintervals.encoding.DriverNotFoundException;
import br.ufrj.dcc.xml.dynamicintervals.vo.Document;
import br.ufrj.dcc.xml.view.AboutForm;
import br.ufrj.dcc.xml.view.ConnectionForm;
import br.ufrj.dcc.xml.view.DynamicForm;
import br.ufrj.dcc.xml.view.Loading;
import br.ufrj.dcc.xml.view.QueryForm;
import br.ufrj.dcc.xml.view.XmlFileChooser;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

public class DynamicAction implements ActionListener, ActionConstants {

	private DynamicForm dForm;
	private QueryForm queryForm;

	private ItemManager manager;

	private XmlFileChooser xmlFileForm;

	private ConnectionForm connectionForm;

	private List<Document> docList;

	private int idDoc;

	private int idCurrentXmlView;

	// Inicia o caso de uso..
	public void start() {
		DynamicForm.changeLookAndFeel("com.birosoft.liquid.LiquidLookAndFeel");
		dForm = new DynamicForm();
		dForm.setListener(this);
		dForm.setVisible(true);
	}
	
	public void showMessageDialog(String message) {
		JOptionPane.showMessageDialog(dForm,
				message);		
	}

	
	public Thread runThreadedAction(int tipoAcao) {
		Loading loading = new Loading(dForm);
		loading.pack();
		
		Runnable r = new ThreadedOperation(tipoAcao, loading);
		Thread t = new Thread(r);
		t.start();
		loading.setVisible(true);
		return t;
	}
	
	private class ThreadedOperation implements Runnable {
		private int tipoAcao;
		private Loading loading;
		public ThreadedOperation(int tipoAcao, Loading loading) {
			super();
			this.tipoAcao = tipoAcao;
			this.loading = loading;
		}
		public void run() {
			try {
				switch (tipoAcao) {
					case ARMAZENAR_XML:
						loading.getTextLabel().setText("Armazenando o documento");
						armazenarXML();
						break;
		
					case LISTAR_XML:
						loading.getTextLabel().setText("Listando os documentos");
						listarXML();
						break;
		
					case RECUPERAR_XML:
						loading.getTextLabel().setText("Recuperando o documento");
						recuperarXML();
						break;
		
					case EXCLUIR_XML:
						loading.getTextLabel().setText("Excluindo o documento");
						excluirXML();
						break;

					case SALVAR_XML:
						loading.getTextLabel().setText("Salvando o documento");
						salvarXML();
						break;
						
				}
			}
			finally {
				loading.setVisible(false);
				loading.dispose();
			}
		}
	}
	
	
	
	/**
	 * Trata os eventos do form..
	 */
	public void actionPerformed(ActionEvent e) {
		int event = Integer.parseInt(e.getActionCommand());

		switch (event) {
		
		case ARMAZENAR_XML:
		case LISTAR_XML:
		case RECUPERAR_XML:
		case EXCLUIR_XML:
		case SALVAR_XML:
			runThreadedAction(event);
			break;		

			
		case MOSTRAR_TELA_BUSCAS:
			preMostrarBuscasDialog();
			break;
			
		case CONECTAR:
			if (connectionForm == null) {
				connectionForm = new ConnectionForm(DatabaseSettings
						.getInstance());
				connectionForm.setListener(this);
			}
			connectionForm.setVisible(true);
			break;

		case CRIAR_ESQUEMA:
			criarEsquema(dForm.getJTextPaneQuery());
			dForm.setButtonState(ESQUEMA_CRIADO);
			break;

		case EXCLUIR_ESQUEMA:
			excluirEsquema(dForm.getJTextPaneQuery());

			// reseta o programa
			dForm.getJTextPaneXml().setText("");
			dForm.deleteAllItems();
			dForm.setButtonState(ESQUEMA_EXCLUIDO);

			if (docList != null) {
				docList.clear();
			}

			break;

		case SAIR:
			dForm.dispose();
			System.exit(0);
			break;

		case SOBRE:
			new AboutForm(dForm).setVisible(true);
			break;

		case CANCELAR_CONECTAR:
			connectionForm.dispose();
			break;

		case LIMPAR_XML:
			dForm.getJTextPaneXml().setText("");
			break;

		case LIMPAR_SQL:
			dForm.getJTextPaneQuery().setText("");
			break;

		case OK_CONECTAR:
			DatabaseSettings dbS_Testado = new DatabaseSettings();

			connectionForm.storeOnDatabaseSettings(dbS_Testado);
			getManager().setSettings(dbS_Testado);
			try {
				// Como faço para alertar o usuário???
				connnect();
				// Se passou no teste da conexão, torno ele a instância padrão
				// do DatabaseSettings.
				DatabaseSettings.setInstance(dbS_Testado);
				connectionForm.setVisible(false);

				System.out.println("Successful Connection!");

				alertaUsuario("Conexão bem sucedida!",
						JOptionPane.PLAIN_MESSAGE, dForm);

				dForm.setButtonState(CONECTADO);

				int resposta = JOptionPane.showConfirmDialog(dForm,
						"Deseja gerar as tabelas em seu schema agora?",
						"Criar Schema", JOptionPane.YES_NO_OPTION);
				if (resposta == 0)
					criarEsquema(dForm.getJTextPaneQuery());

			} catch (Exception ex) {
				getManager().setSettings(DatabaseSettings.getInstance());
				System.out.println(ex.toString());
				alertaUsuario("Não foi possível fazer a conexão: \n" + ex.getLocalizedMessage(),
						JOptionPane.WARNING_MESSAGE, connectionForm);
			}

			break;
		case EXECUTAR_BUSCA:
			executarBusca();
			break;
			
		}
		
	}

	private void executarBusca() {
		int selectedIndex = queryForm.getComboBoxDocument().getSelectedIndex();

		try {
			String query = queryForm.getJTextAreaQuery().getText();
			String result = "";
			List<Document> docs;
			if (selectedIndex >= 1) {
				docs = new ArrayList<Document>();
				docs.add(getDocList().get(selectedIndex-1));
			}
			else {
				docs = getDocList(); 
			}
			for (Document doc : docs) {
				int id = doc.getId();
				switch(queryForm.getComboBoxTipoBusca().getSelectedIndex()) {
					case QueryForm.XPATHSUN:
					{
						String xml = XMLManager.restoreXml(id, 
								dForm.getJTextPaneQuery());
						result += buscaSunXPath(xml, query);
						break;
					}
					case QueryForm.XPATHDATABASE: 
						result += buscaDatabaseXPath(id, query);
						break;		
						
					case QueryForm.XPATHDATABASE2: 
						result += buscaDatabaseXPathMetodo2(id, query);
						break;								
				}
			}
			
		    queryForm.getJTextAreaResult().setText(result);
		    
		    

		} catch (IOException e1) {
			showMessageDialog("Erro de E/S: \n"
					+ e1.getLocalizedMessage());	
			e1.printStackTrace();
		} catch (SQLException e1) {
			showMessageDialog("Erro no acesso ao banco: \n"
					+ e1.getLocalizedMessage());	
			e1.printStackTrace();
		} catch (DriverNotFoundException e1) {
			showMessageDialog("Driver do banco não encontrado: \n"
					+ e1.getLocalizedMessage());	

			e1.printStackTrace();
		} catch (XPathExpressionException e) {
			showMessageDialog("Erro na Query: \n"
					+ e.getLocalizedMessage());	
			e.printStackTrace();
		} 
	}

	private String buscaDatabaseXPath(int id, String query) throws IOException, SQLException {
		return br.ufrj.dcc.xml.dynamicintervals.xpath.XPath.runXPath(query, id, dForm.getJTextPaneQuery());
	}

	private String buscaDatabaseXPathMetodo2(int id, String query) throws IOException, SQLException {
		return br.ufrj.dcc.xml.dynamicintervals.xpath.XPath2.runXPath(query, id, dForm.getJTextPaneQuery());
	}
	
	
	private String buscaSunXPath(String xml, String query) throws XPathExpressionException {
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		XPathExpression expr = xpath.compile(query);
		InputSource source = new InputSource(new ByteArrayInputStream(xml.getBytes())); 
		NodeList nodes = (NodeList) expr.evaluate(source, XPathConstants.NODESET);
		String result = xmlNodeListToString(nodes);
		return result;
	}

	private String xmlNodeListToString(NodeList nodes) {
		String result = "";

		OutputFormat format = new OutputFormat();
		format.setIndenting(true);
		format.setIndent(2);
		format.setOmitDocumentType(true);
		format.setOmitXMLDeclaration(true);

		try {
			for (int i = 0; i < nodes.getLength(); i++) {
			    Node item = nodes.item(i);
			    if (item instanceof Element)  {
				    StringWriter sw = new StringWriter();
					Writer output = new BufferedWriter( sw );
				    XMLSerializer serializer = new XMLSerializer(output, format);
				    serializer.serialize((Element)item);		
				    result+= sw.toString()+'\n';
			    }
			    else
			    	result+= item.toString()+"\n\n";
			}
		} catch (IOException e) {
			// Como usamos na verdade um StringWriter, IOExceptions não vão ocorrer!
			e.printStackTrace();
		}
		return result;
	}

	private void excluirXML() {
		// Se existe algum item selecionado..
		if (dForm.getListSelectedIndex() >= 0) {
			idDoc = getDocList().get(dForm.getListSelectedIndex()).getId();

			XMLManager.deleteXml(idDoc, dForm.getJTextPaneQuery());

			dForm.deleteItem(dForm.getListSelectedIndex());

			// Se estou deletando o documento que está sendo visualizado
			// agora..
			// apago também o xml que está na tela..
			if (idDoc == idCurrentXmlView) {
				dForm.getJTextPaneXml().setText("");
			}

			docList.remove(dForm.getListSelectedIndex());

			if (docList.isEmpty()) {
				dForm.setButtonState(ITEM_NAO_SELECIONADO);
			}
		}
	}

	private void recuperarXML() {
		// Se existe algum item selecionado..
		if (dForm.getListSelectedIndex() >= 0) {
			idDoc = getDocList().get(dForm.getListSelectedIndex()).getId();

			idCurrentXmlView = idDoc;

			try {
				dForm.getJTextPaneXml().setText(XMLManager.restoreXml(idDoc, 
						dForm.getJTextPaneQuery()));
			} catch (IOException e1) {
				showMessageDialog("Erro de E/S: \n"
						+ e1.getLocalizedMessage());	
				e1.printStackTrace();
			} catch (SQLException e1) {
				showMessageDialog("Erro no acesso ao banco: \n"
						+ e1.getLocalizedMessage());	
				e1.printStackTrace();
			} catch (DriverNotFoundException e1) {
				showMessageDialog("Driver do banco não encontrado: \n"
						+ e1.getLocalizedMessage());	

				e1.printStackTrace();
			}
		}
	}

	private void listarXML() {
		showDocList(dForm.getJTextPaneQuery());
	}

	protected void armazenarXML() {
		try {
			xmlFileForm = new XmlFileChooser(dForm.getJTextPaneXml(), XmlFileChooser.OPEN);
			xmlFileForm.setVisible(true);

			idDoc = XMLManager.storeXml(xmlFileForm.getPath(), dForm
					.getJTextPaneQuery());

			String xmlFile = xmlFileForm.getPath();

			if (xmlFile.lastIndexOf(File.separator) > 0) {
				xmlFile = xmlFile.substring(xmlFile.lastIndexOf(File.separator) + 1);
			}

			xmlFile = xmlFile.replace('.', '_');

			Document doc = new Document(idDoc, xmlFile);

			dForm.setDocElemet(doc);

			getDocList().add(doc);

		} catch (SAXParseException ex) {
			showMessageDialog("Este Documento não é bem formado.\n"
							+ "Erro encontrado na linha: "
							+ ex.getLineNumber() + " coluna: "
							+ ex.getColumnNumber()
							+ " \nErro: " + ex.getLocalizedMessage());
			ex.printStackTrace();
		} catch (DatabaseException ex) {
			showMessageDialog("Erro no acesso ao banco de dados: \n"
							+ ex.getLocalizedMessage());
			ex.printStackTrace();
		} catch (SAXException e1) {
			showMessageDialog("Erro no Parser: \n"
					+ e1.getLocalizedMessage());				
			e1.printStackTrace();
		} catch (ParserConfigurationException epx) {
			showMessageDialog("Erro de configuração do Parser: \n"
					+ epx.getLocalizedMessage());
			epx.printStackTrace();
		} catch (IOException eio) {
			showMessageDialog("Erro de E/S: \n"
					+ eio.getLocalizedMessage());	
			eio.printStackTrace();
		}
	}

	protected void salvarXML() {
		xmlFileForm = new XmlFileChooser(dForm.getJTextPaneXml(), XmlFileChooser.SAVE);
		xmlFileForm.setVisible(true);
		String path = xmlFileForm.getPath();
		
		
		try {
			FileOutputStream out = new FileOutputStream(path);
			out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".getBytes());
			out.write(dForm.getJTextPaneXml().getText().getBytes());
			out.close();
		} catch (FileNotFoundException e) {
			showMessageDialog("Arquivo não encontrado: \n"
					+ e.getLocalizedMessage());	
			e.printStackTrace();
		} catch (IOException e) {
			showMessageDialog("Erro ao gravar o arquivo: \n"
					+ e.getLocalizedMessage());	
			e.printStackTrace();
		}
		
	}	
	
	private void showDocList(JTextComponent pane) {
		try {
			ItemManager manager = new ItemManager();
			manager.getDefaultConnection();
			docList = manager.selectDocuments();
			pane.setText(manager.getOutputText());
		} catch (SQLException ex) {
			ex.printStackTrace();
		}

		dForm.setDocList(docList);
	}

	private void criarEsquema(JTextArea out) {
		
		long before = System.nanoTime();
		
		try {
			getManager().createTables();
			alertaUsuario("Tabelas criadas com sucesso!",
					JOptionPane.PLAIN_MESSAGE, dForm);
		} catch (SQLException e) {
			String erro = e.getSQLState();
			if (erro.equals("08001"))// No suitable driver
				alertaUsuario("É necessário conectar ao servidor primeiro!",
						JOptionPane.WARNING_MESSAGE, dForm);
			else if (erro.equals("42P07"))// ERROR: relation "documents"
				// already exists
				alertaUsuario(
						"Já existem tabelas com o mesmo nome no schema desejado!",
						JOptionPane.WARNING_MESSAGE, dForm);
			else
				alertaUsuario("Não foi possível criar as tabelas!",
						JOptionPane.WARNING_MESSAGE, dForm);
			System.out.println(e.toString());
		}

		long after = System.nanoTime();
		StatisticsCollector.getInstance().add("Geração do esquema",
				after - before);
		
		out.setText(getManager().getOutputText());
		
		try {
			StatisticsCollector.getInstance().save();
		} catch (IOException e1) {
			System.out
					.println("Erro ao salvar arquivo de benchmark (criação do esquema)");
			e1.printStackTrace();
		}
		StatisticsCollector.resetInstance("statistics-schema");

	}

	private void alertaUsuario(String msg, int tipo, Component parent) {
		JOptionPane.showMessageDialog(parent, msg, "Atenção", tipo);
	}

	private void connnect() throws ClassNotFoundException, SQLException {
		getManager().testDriver();
		getManager().getNewConnection();
	}

	private ItemManager getManager() {
		if (manager == null) {
			manager = new ItemManager();
		}
		return manager;
	}

	private void excluirEsquema(JTextComponent pane) {
		try {
			getManager().dropTables();
			pane.setText(getManager().getOutputText());
			alertaUsuario("Tabelas excluídas com sucesso!",
					JOptionPane.PLAIN_MESSAGE, dForm);
		} catch (SQLException e) {
			String erro = e.getSQLState();
			if (erro.equals("08001"))// No suitable driver
				alertaUsuario("É necessário conectar ao servidor primeiro!",
						JOptionPane.WARNING_MESSAGE, dForm);
			else if (erro.equals("2BP01"))// ERROR: cannot drop table
				// documents because other objects
				// depend on it
				alertaUsuario(
						"Existem dependências que não permitem a exclusão das tabelas!",
						JOptionPane.WARNING_MESSAGE, dForm);
			else if (erro.equals("42P01"))// ERROR: table "items" does not
				// exist
				alertaUsuario("Uma ou mais tabelas não existe(m)!",
						JOptionPane.WARNING_MESSAGE, dForm);
			else
				alertaUsuario("Não foi possível criar as tabelas!",
						JOptionPane.WARNING_MESSAGE, dForm);
			System.out.println(e.toString());
		}
	}

	public List<Document> getDocList() {
		if (docList == null) {
			docList = new ArrayList<Document>();
		}
		return docList;
	}
	
	public void preMostrarBuscasDialog() {
		
		if (getDocList().isEmpty()) {
			final Thread w = runThreadedAction(LISTAR_XML);
			Runnable showDialogRunnable = new Runnable() {
				public void run() {
					try {
						w.join();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					mostrarBuscasDialog();
				}
			};
			Thread t = new Thread(showDialogRunnable);
			t.start();
		}
		else {
			mostrarBuscasDialog();
		}
	}
	
	public void mostrarBuscasDialog() {
		
		queryForm = new QueryForm();
		queryForm.setListener(this);
		queryForm.getComboBoxDocument().addItem("Todos os documentos");
		List<Document> docList =  getDocList();
		
		if (docList!=null) {
			for (Document document : docList) {
				queryForm.getComboBoxDocument().addItem(document);
			}
		}
		
		queryForm.setVisible(true);
	}
	
}
